﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Documents;
using SDF.Pagination;

namespace SDF
{
    public abstract class SmartBlock: SmartTextElement, IValidateParent, IHarvestable
    {
        #region Constructor

        protected SmartBlock()
        { 
        }

        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty BreakColumnBeforeProperty = Block.BreakColumnBeforeProperty.AddOwner(typeof(SmartBlock));
        public static readonly DependencyProperty BreakPageBeforeProperty = Block.BreakPageBeforeProperty.AddOwner(typeof(SmartBlock));

        public static readonly DependencyProperty MarginProperty = DependencyProperty.Register(
            "Margin",
            typeof(Thickness),
            typeof(SmartBlock),
            new FrameworkPropertyMetadata(new Thickness(double.NaN), FrameworkPropertyMetadataOptions.AffectsMeasure, SmartTextElement.OnMeasureAffected),
            new ValidateValueCallback(ValidateMargin));

        private static bool ValidateMargin(object value)
        {
            var thickness = (Thickness)value;
            if (double.IsInfinity(thickness.Bottom) ||
                double.IsInfinity(thickness.Left) ||
                double.IsInfinity(thickness.Right) ||
                double.IsInfinity(thickness.Top))
                return false;

            if (thickness.Bottom < 0 ||
                thickness.Left < 0 ||
                thickness.Right < 0 ||
                thickness.Top< 0)
                return false;

            return true;
        }

        public static readonly DependencyProperty PaddingProperty = DependencyProperty.Register(
            "Padding",
            typeof(Thickness),
            typeof(SmartBlock),
            new FrameworkPropertyMetadata(new Thickness(0), FrameworkPropertyMetadataOptions.AffectsMeasure, SmartTextElement.OnMeasureAffected),
            new ValidateValueCallback(ValidatePadding));

        private static bool ValidatePadding(object value)
        {
            var thickness = (Thickness)value;
            if (double.IsInfinity(thickness.Bottom) ||
                double.IsInfinity(thickness.Left) ||
                double.IsInfinity(thickness.Right) ||
                double.IsInfinity(thickness.Top))
                return false;

            if (thickness.Bottom < 0 ||
                thickness.Left < 0 ||
                thickness.Right < 0 ||
                thickness.Top < 0)
                return false;

            return true;
        }

        #region "Actual" Properties

        private static readonly DependencyPropertyKey ActualMarginPropertyKey = DependencyProperty.RegisterReadOnly(
            "ActualMargin",
            typeof(Thickness),
            typeof(SmartBlock),
            new FrameworkPropertyMetadata(new Thickness(0)));

        public static readonly DependencyProperty ActualMarginProperty = ActualMarginPropertyKey.DependencyProperty;

        private static readonly DependencyPropertyKey ActualPaddingPropertyKey = DependencyProperty.RegisterReadOnly(
            "ActualPadding",
            typeof(Thickness),
            typeof(SmartBlock),
            new FrameworkPropertyMetadata(new Thickness(0)));

        public static readonly DependencyProperty ActualPaddingProperty = ActualPaddingPropertyKey.DependencyProperty;

        #endregion

        #endregion

        #region Properties

        public Thickness ActualMargin
        {
            get { return (Thickness)GetValue(ActualMarginProperty); }
            private set { SetValue(ActualMarginPropertyKey, value); }
        }

        public Thickness ActualPadding
        {
            get { return (Thickness)GetValue(ActualPaddingProperty); }
            private set { SetValue(ActualPaddingPropertyKey, value); }
        }

        public bool BreakColumnBefore
        {
            get { return (bool)GetValue(BreakColumnBeforeProperty); }
            set { SetValue(BreakColumnBeforeProperty, value); }
        }

        public bool BreakPageBefore
        {
            get { return (bool)GetValue(BreakPageBeforeProperty); }
            set { SetValue(BreakPageBeforeProperty, value); }
        }

        public Thickness Margin
        {
            get { return (Thickness)GetValue(MarginProperty); }
            set { SetValue(MarginProperty, value); }
        }

        public Thickness Padding
        {
            get { return (Thickness)GetValue(PaddingProperty); }
            set { SetValue(PaddingProperty, value); }
        }

        #endregion

        #region Parent/Child Implementation

        public IBlockContainer Parent 
        {
            get { return SmartLogicalTreeHelper.GetParent(this) as IBlockContainer; }
        }

        public virtual bool IsValidParent(DependencyObject parent)
        {
            return (parent is IBlockContainer);
        }

        public bool IsFirstChild 
        {
            get
            {
                if ((Parent == null) || (Parent.Blocks == null) || (Parent.Blocks.Count == 0))
                    return true;

                var firstChild = Parent.Blocks[0];
                return (firstChild == this);
            }
        }

        public bool IsLastChild 
        {
            get
            {
                if ((Parent == null) || (Parent.Blocks == null) || (Parent.Blocks.Count == 0))
                    return true;

                var lastChild = Parent.Blocks[Parent.Blocks.Count - 1];
                return (lastChild == this);
            }
        }

        #endregion

        #region Layout Methods

        protected sealed override Size MeasureCore(Size availableSize)
        {
            ActualMargin = CalculateActualMargin();
            ActualPadding = CalculateActualPadding();

            Size constraint = availableSize;
            constraint.Height = Math.Max(0, constraint.Height - (ActualMargin.Top + ActualMargin.Bottom));
            constraint.Width = Math.Max(0, constraint.Width - (ActualMargin.Left + ActualMargin.Right));

            // The MeasureOverride measures the "content" of the block. Thus it is given the
            // available size minus the margin
            Size result = MeasureOverride(constraint);

            // Now we add the margin again and return the result
            result.Height += (ActualMargin.Top + ActualMargin.Bottom);
            result.Width +=  (ActualMargin.Left+ ActualMargin.Right);
            return result;
        }

        protected abstract Size MeasureOverride(Size availableSize);

        protected override sealed void ArrangeCore(Rect finalRect)
        {
            base.VisualOffset = new Vector(finalRect.X + ActualMargin.Left, finalRect.Y + ActualMargin.Top);

            Size size = new Size(Math.Max(0, finalRect.Width - (ActualMargin.Left + ActualMargin.Right)),
                                 Math.Max(0, finalRect.Height - (ActualMargin.Top + ActualMargin.Bottom)));

            // The ArrangeOverride arranges the "content" of the block. Thus it is given the
            // available size minus the margin.
            var finalSize = ArrangeOverride(size);
            //finalSize.Height = Math.Max(0, finalSize.Height + (ActualMargin.Top + ActualMargin.Bottom));
            //finalSize.Width = Math.Max(0, finalSize.Width + (ActualMargin.Left + ActualMargin.Right));
            base.RenderSize = finalSize;
        }

        protected abstract Size ArrangeOverride(Size availableSize);

        #endregion

        #region Margin and Padding Methods

        protected virtual Thickness CalculateActualMargin()
        {
            double left = Margin.Left;
            double top = Margin.Top;
            double right = Margin.Right;
            double bottom = Margin.Bottom;

            if (double.IsNaN(left))
                left = 0;
            if (double.IsNaN(top))
                top = GetDefaultTopMargin();
            if (double.IsNaN(right))
                right = 0;
            if (double.IsNaN(bottom))
                bottom = GetDefaultBottomMargin();

            return new Thickness(left, top, right, bottom);
        }

        protected virtual double GetDefaultTopMargin()
        {
            if (IsFirstChild)
                return 0;

            return LineHeight;
        }

        protected virtual double GetDefaultBottomMargin()
        {
            if (IsLastChild)
                return 0;

            return LineHeight;
        }

        protected virtual Thickness CalculateActualPadding()
        {
            double left = Padding.Left;
            double top = Padding.Top;
            double right = Padding.Right;
            double bottom = Padding.Bottom;

            if (double.IsNaN(left))
                left = 0;
            if (double.IsNaN(top))
                top = 0;
            if (double.IsNaN(right))
                right = 0;
            if (double.IsNaN(bottom))
                bottom = 0;

            return new Thickness(left, top, right, bottom);
        }

        #endregion

        #region IHarvestable

        public abstract HarvestResult Harvest(HarvestingContext context, double remainingHeight, SmartBlock previousBlock);

        public virtual bool IsPartiallyHarvested(HarvestingContext context)
        {
            if (context.IsFullyHarvested(this))
                return false;

            foreach (DependencyObject child in SmartLogicalTreeHelper.GetChilden(this))
            {
                if (context.IsFullyHarvested(child))
                    return true;

                var harvestable = child as IHarvestable;
                if ((harvestable != null) && (harvestable.IsPartiallyHarvested(context)))
                    return true;
            }

            return false;
        }

        #endregion

        #region GetEmptyCopy Method

        public abstract SmartBlock GetEmptyCopy();

        #endregion

        #region CalculateMinHeight Method

        public abstract double CalculateMinHeight();

        #endregion
    }
}
